using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Pdb;

namespace LoggingMagic.AssemlyOperations
{
    internal class AssemblyAccessor
    {
        public class AssemblyInfo
        {
            public AssemblyDefinition AssemblyDefinition;
            public IAssemblyResolver AssemblyResolver;
        }

        public AssemblyInfo ParseAssembly(string assemblyPath, string references)
        {
            var parameters = MakeReaderParameters(assemblyPath, references);

            var assembly = AssemblyDefinition.ReadAssembly(assemblyPath, parameters);

            return new AssemblyInfo { AssemblyDefinition = assembly, AssemblyResolver = parameters.AssemblyResolver };
        }


        private ReaderParameters MakeReaderParameters(string file, string references = null)
        {
            var result = new ReaderParameters
            {
                ReadingMode = ReadingMode.Deferred,
                AssemblyResolver = new CustomAssemblyResolver(references)
            };

            if (File.Exists(Path.ChangeExtension(file, ".pdb")))
            {
                result.ReadSymbols = true;
                result.SymbolReaderProvider = new PdbReaderProvider();
            }

            return result;
        }

        public WriterParameters MakeWriterParameters(string file, string key = null)
        {
            if (File.Exists(Path.ChangeExtension(file, ".pdb")))
                return new WriterParameters
                {
                    WriteSymbols = true,
                    SymbolWriterProvider = new PdbWriterProvider(),
                    StrongNameKeyPair = LoadSnKey(key)
                };

            return new WriterParameters
            {
                WriteSymbols = false,
                StrongNameKeyPair = LoadSnKey(key)
            };
        }

        private StrongNameKeyPair LoadSnKey(string key)
        {
            return key != null ? new StrongNameKeyPair(File.ReadAllBytes(key)) : null;
        }

        class CustomAssemblyResolver : DefaultAssemblyResolver
        {
            readonly Dictionary<string, string> _assemblies;

            public CustomAssemblyResolver(string references)
            {
                _assemblies = (references ?? "").Split(';').ToDictionary(i => Path.GetFileNameWithoutExtension(i), StringComparer.InvariantCultureIgnoreCase);

                ResolveFailure += LoadFromReference;
            }

            AssemblyDefinition LoadFromReference(object sender, AssemblyNameReference reference)
            {
                string result;

                if (_assemblies.TryGetValue(reference.Name, out result))
                    return ModuleDefinition.ReadModule(result, new ReaderParameters { AssemblyResolver = this }).Assembly;

                return null;
            }
        }
    }
}